// Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
// This source file is part of the Cangjie project, licensed under Apache-2.0
// with Runtime Library Exception.
//
// See https://cangjie-lang.cn/pages/LICENSE for license information.

#include <array>
#include <cstdio>
#include <memory>
#include <string>
#include <vector>

#if defined(PLATFORM_WINDOWS)
#include <windows.h>
#elif defined(PLATFORM_LINUX) || defined(PLATFORM_MACOS)
#include <sys/wait.h>
#include <unistd.h>
#endif

#include "Platform/System.h"
#include "Platform/EnvironmentVariable.h"

#include "Platform/System.Run.h"

namespace Platform::System {

static void PushBufferLines(std::vector<std::string> &out, const char *data, size_t sz)
{
    static std::string pending;
    for (size_t i = 0; i < sz; ++i) {
        const char ch = data[i];
        if (ch != '\n') {
            pending.push_back(ch);
            continue;
        }
        if (!pending.empty() && pending.back() == '\r') {
            pending.pop_back();
        }
        out.push_back(std::move(pending));
        pending.clear();
    }
}

#if defined(PLATFORM_WINDOWS)

ProcessResult Run(const std::string &cmd,
                  std::optional<Path> workingDir,
                  std::optional<std::vector<EnvironmentVariable>> extraEnvVars)
{
    std::string shellCmd = cmd;

    // ---------- Prepend env variable export ----------
    if (extraEnvVars) {
        for (const auto& ev : *extraEnvVars) {
            shellCmd = ev.GenerateCommand() + CmdChainIfSuccess + shellCmd;
        }
    }

    // ---------- Prepend cd ----------
    if (workingDir) {
        shellCmd = "cd /d \"" + workingDir->string() + "\"" + CmdChainIfSuccess + shellCmd;
    }

    // ---------- Build full command line ----------
    std::string fullCmd = "cmd /C \"" + shellCmd + "\"";

    // ---------- Create pipe ----------
    SECURITY_ATTRIBUTES sa{sizeof(sa), nullptr, TRUE};
    HANDLE hRead{}, hWrite{};
    if (!CreatePipe(&hRead, &hWrite, &sa, 0)) {
        return {
            .success = false
        };
    }

    if (!SetHandleInformation(hRead, HANDLE_FLAG_INHERIT, 0)) {
        CloseHandle(hRead); CloseHandle(hWrite);
        return {
            .success = false
        };
    }

    // ---------- Setup STARTUPINFO ----------
    STARTUPINFOA si{};
    si.cb = sizeof(si);
    si.dwFlags |= STARTF_USESTDHANDLES;
    si.hStdOutput = hWrite;
    si.hStdError  = GetStdHandle(STD_ERROR_HANDLE);

    PROCESS_INFORMATION pi{};
    auto cmdLine = _strdup(fullCmd.c_str());

    BOOL ok = CreateProcessA(nullptr, cmdLine, nullptr, nullptr, TRUE,
                             CREATE_NO_WINDOW, nullptr, nullptr,
                             &si, &pi);
    free(cmdLine);
    CloseHandle(hWrite);

    if (!ok) {
        CloseHandle(hRead);
        return {
            .success = false
        };
    }

    // ---------- Read output ----------
    std::vector<std::string> output;
    constexpr DWORD BufSize = 4096;
    char buffer[BufSize];
    DWORD bytesRead{};
    while (ReadFile(hRead, buffer, BufSize, &bytesRead, nullptr) && bytesRead) {
        PushBufferLines(output, buffer, bytesRead);
    }
    CloseHandle(hRead);

    // ---------- Wait and cleanup ----------
    WaitForSingleObject(pi.hProcess, INFINITE);
    DWORD exitCode{};
    GetExitCodeProcess(pi.hProcess, &exitCode);

    CloseHandle(pi.hThread);
    CloseHandle(pi.hProcess);

    return {.success = exitCode == 0, .exitCode = static_cast<int>(exitCode), .output = std::move(output)};
}

#elif defined(PLATFORM_LINUX) || defined(PLATFORM_MACOS)

ProcessResult Run(const std::string &cmd,
                  std::optional<Path> workingDir,
                  std::optional<std::vector<EnvironmentVariable>> extraEnvVars)
{
    std::string shellCmd = cmd;
    if (extraEnvVars) {
        for (const auto& ev : *extraEnvVars) {
            shellCmd = ev.GenerateCommand() + CmdChainIfSuccess + shellCmd;
        }
    }
    if (workingDir) {
        shellCmd = "cd '" + workingDir->string() + '\'' + CmdChainIfSuccess + shellCmd;
    }

    FILE *pipe = popen(shellCmd.c_str(), "r");
    if (!pipe) {
        return {
            .success = false
        };
    }

    std::vector<std::string> output;
    constexpr size_t BufSize = 4096;
    std::array<char, BufSize> buf{};
    while (size_t n = fread(buf.data(), 1, buf.size(), pipe)) {
        PushBufferLines(output, buf.data(), n);
    }

    int status = pclose(pipe);
    bool exited = WIFEXITED(status);
    int code = exited ? WEXITSTATUS(status) : -1;

    return {.success = exited && code == 0, .exitCode = code, .output = std::move(output)};
}
#endif

} // namespace Platform::System
